#ifndef __VANILA_ENVIRONMENT_HH__
#define __VANILA_ENVIRONMENT_HH__

#include "vanila/token.h"
#include "vanila/object.h"
#include <vector>
#include <cstdint>

namespace vanila
{
#define UINT8_COUNT (UINT8_MAX + 1)

enum class FunctionType
{
    FUNCTION,
    INITIALIZER,
    METHOD,
    SCRIPT,
};

struct Local
{
    Token name;
    int depth;
    bool isCaptured;
    Local(const Token& n, int d, bool ic = false) noexcept : name{n}, depth{d}, isCaptured{ic}
    {}
};

struct Upvalue
{
    uint8_t index;
    bool isLocal;
    Upvalue(uint8_t i, bool il) noexcept : index{i}, isLocal{il}
    {}
};

struct ClassEnvironment
{
    ClassEnvironment* enclosing;
    bool hasSuperclass;
    ClassEnvironment(ClassEnvironment* e = nullptr) : enclosing{e}, hasSuperclass{false}
    {}
};

class Environment
{
public:
    enum {RESOLVE_ERROR = -2};

    //! \brief Construct a new Environment object
    Environment(FunctionType functionType);

    //! \brief begin a new scope
    void beginScope() noexcept
    { this->_scopeDepth++; }

    //! \brief end a scope
    void endScope() noexcept
    { this->_scopeDepth--; }

    //! \brief add a local varibale's token
    bool addLocal(const Token& name);
    void addLocal(const Local& local)
    { this->_locals.push_back(local); }

    //! \brief find the position of sepecify token name
    int resolveLocal(const Token& name);

    //! \brief find the upvalue's position
    int resolveUpvalue(const Token& name);

    //! \brief mark the last local variable already been init
    void markInitialized();

public:
    const Local& lastLocal() const noexcept
    { return this->_locals.back(); }

    void popLastLocal() noexcept
    { this->_locals.pop_back(); }

private:
    //! \brief add a upvalue in vector, and return its index
    int addUpvalue(uint8_t index, bool isLocal);

private:
    Environment* _enclosing;
    ObjectFunction* _function;
    FunctionType _functionType;
    std::vector<Local> _locals;
    std::vector<Upvalue> _upvalues;
    uint32_t _scopeDepth;

public:
    Environment* enclosing() noexcept
    { return this->_enclosing; }

    void setEnclosing(Environment* enclosing) noexcept
    { this->_enclosing = enclosing; }

    ObjectFunction* function() noexcept
    { return this->_function; }
    
    const ObjectFunction* function() const noexcept
    { return this->_function; }

    FunctionType functionType() const noexcept
    { return this->_functionType; }

    const std::vector<Local>& locals() const noexcept
    { return this->_locals; }
    
    std::vector<Local>& locals() noexcept
    { return this->_locals; }

    const std::vector<Upvalue>& upvalues() const noexcept
    { return this->_upvalues; }
    
    std::vector<Upvalue>& upvalues() noexcept
    { return this->_upvalues; }    

    uint32_t localCount() const noexcept
    { return this->_locals.size(); }

    int scopeDepth() const noexcept
    { return this->_scopeDepth; }
};

}

#endif